﻿using System;
using System.Collections;
using System.Collections.Generic;

using BizElements.Core;

namespace BizElements.BusinessLayer
{
    /// <summary>Safe, delegate based implementation of generic interface for managing children collections that belong to a composite entity object.</summary>
    /// <typeparam name="TParent">Parent entity class that implements <see cref="IBusinessObject"/> interface.</typeparam>
    /// <typeparam name="TChild">Child entity class that implements <see cref="IDataObject"/> interface.</typeparam>
    public sealed class ChildrenAdapter<TParent, TChild> : IChildrenAdapter
        where TParent : IBusinessObject
        where TChild : IDataObject, new()
    {
        #region Members - delegates.

        Delegates.Function<IList<TChild>, TParent> getChildren;
        Delegates.Function<DbRelation> getForeignKey;
        Delegates.Procedure<TParent> fetchChildren;
        Delegates.Procedure<TParent> ensureChildren;
        Delegates.Procedure<TParent> ensureAllParentsForChildren;
        Delegates.Procedure<TParent, DbRelation[]> ensureParentsForChildren;
        Delegates.Procedure<TParent> saveChildren;
        Delegates.Function<BusinessRuleCollection, TParent, ValidationLevel> validateChildren;
        Delegates.Function<TChild, TParent> newChild;
        Delegates.Procedure<TParent> ensureBusinessObjectsInChildCollection;

        #endregion

        #region CTor.

        /// <summary>
        /// Initializes a new instance of <b>ChildrenAdapter</b> class.
        /// </summary>
        /// <param name="propertyName">Child entity collection property name.</param>
        /// <param name="getForeignKey">Method or lambda expression which gets relation through which child entities are connected to parent table.</param>
        /// <param name="getChildren">Method or lambda expression which gets children collection. The collection <b>must</b> implement non-generic <b>IList</b> interface, too.</param>
        /// <param name="fetchChildren">Method or lambda expression which fetches children from data source.</param>
        /// <param name="ensureChildren">Method or lambda expression which ensure that children have been fetched form data source.</param>
        /// <param name="ensureAllParentsForChildren">Method or lambda expression which ensures that all parents for all entities in the collection are initialized, ie. fetched.</param>
        /// <param name="ensureParentsForChildren">Method or lambda expression which ensures that parents specified by the provided relations for all entities in the collection are initialized, ie. fetched.</param>
        /// <param name="saveChildren">Method or lambda expression which saves children to data source.</param>
        /// <param name="validateChildren">Method or lambda expression which validates child collection.</param>
        /// <param name="newChild">Method or lambda expression which initializes a new child entity.</param>
        /// <param name="ensureBusinessObjectsInChildCollection">Optional method or lambda expression which ensusres that all elements in the collection implement <see cref="IDataObject"/> interface. Set to <b>null</b> if all child items always implement <see cref="ISelfValidatingObject"/>.</param>
        public ChildrenAdapter(string propertyName,
            Delegates.Function<DbRelation> getForeignKey,
            Delegates.Function<IList<TChild>, TParent> getChildren,
            Delegates.Procedure<TParent> fetchChildren, 
            Delegates.Procedure<TParent> ensureChildren, 
            Delegates.Procedure<TParent> ensureAllParentsForChildren,
            Delegates.Procedure<TParent, DbRelation[]> ensureParentsForChildren, 
            Delegates.Procedure<TParent> saveChildren,
            Delegates.Function<BusinessRuleCollection, TParent, ValidationLevel> validateChildren,
            Delegates.Function<TChild, TParent> newChild, 
            Delegates.Procedure<TParent> ensureBusinessObjectsInChildCollection)
        {
            this.PropertyName = propertyName;
            this.getForeignKey = getForeignKey;
            this.getChildren = getChildren;
            this.fetchChildren = fetchChildren;
            this.ensureChildren = ensureChildren;
            this.ensureAllParentsForChildren = ensureAllParentsForChildren;
            this.ensureParentsForChildren = ensureParentsForChildren;
            this.saveChildren = saveChildren;
            this.validateChildren = validateChildren;
            this.newChild = newChild;
            this.ensureBusinessObjectsInChildCollection = ensureBusinessObjectsInChildCollection;
        }

        #endregion

        /// <summary>Gets child collection property name.</summary>
        public string PropertyName { get; private set; }

        #region IChildrenAdapter methods.

        /// <summary>Gets relation through which child entities are connected to parent table.</summary>
        /// <returns>Database relation.</returns>
        public DbRelation GetForeignKey()
        {
            return this.getForeignKey();
        }        

        /// <summary>Gets child collection that belongs to the specified parent entity.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection worked by current adapter.</param>
        /// <returns>Children entity collection.</returns>
        public IList<TChild> GetChildren(TParent parentEntity)
        {
            return this.getChildren(parentEntity);
        }

        /// <summary>Fetches children that belong to the specified parent entity from data source.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        public void FetchChildren(TParent parentEntity)
        {
            this.fetchChildren(parentEntity);
        }

        /// <summary>Ensures that the child collection has been fetched from data source.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        public void EnsureChildren(TParent parentEntity)
        {
            this.ensureChildren(parentEntity);
        }

        /// <summary>Persists children to data source if partial saving of composite entity is allowed and implemented by provided composite entity.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        /// <param name="actionInitiator">Action initiator. This may be an application user, software module, 3rd party application etc. which is initiating the save action. <b>Null</b> is not allowed.</param>
        /// <param name="enforceSecurityChecks">Indicates whether the actor's permissions to save the current object are to be checked. Set to <b>false</b> only if client is trusted and the security checks have been executed elsewhere.</param>
        /// <exception cref="BrokenRuleException">The given actor failed the security checks. Generated exception contains details about broken rules/permissions.</exception>
        public void SaveChildren(TParent parentEntity, IActor actionInitiator, bool enforceSecurityChecks)
        {
            if (enforceSecurityChecks)
            {
                var brokenSecurityRules = parentEntity.GrantSave(actionInitiator);
                if (brokenSecurityRules.HasBrokenRules)
                    throw new BrokenRuleException(brokenSecurityRules.GetBrokenRules());
            }

            this.ensureBusinessObjectsInChildCollection(parentEntity);
            foreach (IDataObject child in getChildren(parentEntity))
            {
                if (child.EntityState != EntityState.Synchronized)
                    child.SetModifiedBy(actionInitiator);
            }

            this.saveChildren(parentEntity);
        }

        /// <summary>Validates child entities that belong to the specified parent entity if partial validation of composite entity is allowed and implemented.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        /// <param name="level">Specifies the detail of validation.</param>
        /// <returns>Collection of broken rules.</returns>
        public BusinessRuleCollection ValidateChildren(TParent parentEntity, ValidationLevel level)
        {
            return this.validateChildren(parentEntity, level);
        }

        /// <summary>Creates a new instance of child entity but it's not automatically added to the collection.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        /// <param name="createdBy">Actor that creates a new object.</param>
        /// <returns>A child entity.</returns>
        public TChild NewChild(TParent parentEntity, IActor createdBy)
        {
            var child = newChild(parentEntity);
            child.SetModifiedBy(createdBy);
            return child;
        }

        /// <summary>Ensures that child collection contains objects that implement <see cref="IDataObject"/> interface. The method will attempt to convert simple, BizElements generated, entities to business objects.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        public void EnsureBusinessObjectsInChildCollection(TParent parentEntity)
        {
            if (this.ensureBusinessObjectsInChildCollection != null)
                this.ensureBusinessObjectsInChildCollection(parentEntity);
        }

        /// <summary>Ensures that all parent entities for children are initialized, ie. fetched.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        public void EnsureAllParentsForChildren(TParent parentEntity)
        {
            this.ensureAllParentsForChildren(parentEntity);
        }

        /// <summary>Ensures that all specified parent entities for children are fetched, ie. fetched.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        /// <param name="relationsToOtherParents">Relations to other parents.</param>
        public void EnsureParentsForChildren(TParent parentEntity, params DbRelation[] relationsToOtherParents)
        {
            this.ensureParentsForChildren(parentEntity, relationsToOtherParents);
        }

        #endregion

        #region IChildrenAdapter - explicit implementation.

        /// <summary>Gets specified children.</summary>
        /// <returns>Children entity collection.</returns>
        IList IChildrenAdapter.GetChildren(IBusinessObject parentEntity)
        {
            return (IList)GetChildren((TParent)parentEntity);
        }

        /// <summary>Fetches specified children from data source.</summary>
        void IChildrenAdapter.FetchChildren(IBusinessObject parentEntity)
        {
            FetchChildren((TParent)parentEntity);
        }

        /// <summary>Ensures that the specified children collection has been fetched from data source.</summary>
        void IChildrenAdapter.EnsureChildren(IBusinessObject parentEntity)
        {
            EnsureChildren((TParent)parentEntity);
        }

        /// <summary>Persists specified children to data source if partial saving of composite entity is allowed and implemented.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection worked by current adapter.</param>
        /// <param name="actionInitiator">Action initiator. This may be an application user, software module, 3rd party application etc. which is initiating the save action. <b>Null</b> is not allowed.</param>
        /// <param name="enforceSecurityChecks">Indicates whether the actor's permissions to save the current object are checked. Set to <b>false</b> only if client is trusted and the security checks have been executed elsewhere.</param>
        /// <exception cref="BrokenRuleException">The given actor failed the security checks. Generated exception contains details about broken rules/permissions.</exception>
        void IChildrenAdapter.SaveChildren(IBusinessObject parentEntity, IActor actionInitiator, bool enforceSecurityChecks)
        {
            SaveChildren((TParent)parentEntity, actionInitiator, enforceSecurityChecks);
        }

        /// <summary>Validates specified child entities.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection worked by current adapter.</param>
        /// <param name="level">Specifies the detail of validation.</param>
        /// <returns>Collection of broken rules.</returns>
        BusinessRuleCollection IChildrenAdapter.ValidateChildren(IBusinessObject parentEntity, ValidationLevel level)
        {
            return ValidateChildren((TParent)parentEntity, level);
        }

        /// <summary>Creates a new instance of child entity but it shouldn't be automatically added to the collection.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection worked by current adapter.</param>
        /// <param name="createdBy">An actor which created the object.</param>
        /// <returns>A child entity.</returns>
        IDataObject IChildrenAdapter.NewChild(IBusinessObject parentEntity, IActor createdBy)
        {
            return NewChild((TParent)parentEntity, createdBy);
        }

        /// <summary>Ensures that child collection contains objects that implement <see cref="IDataObject"/> interface. The method must attempt to convert simple, BizElements generated, entities to business objects.</summary>
        void IChildrenAdapter.EnsureBusinessObjectsInChildCollection(IBusinessObject parentEntity)
        {
            EnsureBusinessObjectsInChildCollection((TParent)parentEntity);
        }

        /// <summary>Ensure that all parent entities for children are fetched.</summary>
        void IChildrenAdapter.EnsureAllParentsForChildren(IBusinessObject parentEntity)
        {
            EnsureAllParentsForChildren((TParent)parentEntity);
        }

        /// <summary>Ensure that all specified parent entities for children are fetched.</summary>
        void IChildrenAdapter.EnsureParentsForChildren(IBusinessObject parentEntity, DbRelation[] relationsToOtherParents)
        {
            EnsureParentsForChildren((TParent)parentEntity, relationsToOtherParents);
        }

        #endregion

        #region Clone.

        /// <summary>Creates a new object that is a copy of the current instance.</summary>
        /// <returns>A new object that is a copy of the current instance.</returns>
        public IChildrenAdapter Clone()
        {
            ChildrenAdapter<TParent, TChild> clone = new ChildrenAdapter<TParent, TChild>(
                this.PropertyName,
                this.getForeignKey,
                this.getChildren,
                this.fetchChildren,
                this.ensureChildren,
                this.ensureAllParentsForChildren,
                this.ensureParentsForChildren,
                this.saveChildren,
                this.validateChildren,
                this.newChild,
                this.ensureBusinessObjectsInChildCollection);

            return clone;
        }

        #endregion
    }
}